#include "Shader.h"
#include <vector>
#include <string>
#include <fstream>
#include <cmath>
#include <cstdlib>
#include <GL/glew.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>


bool mgl::Shader::processUniformToken(const std::string& path, std::string& str){
	std::ifstream ifs;
	std::stringstream ss;

	ifs.open(path, std::ios::in);
	if(ifs.is_open() == false)  return false;

	ss << ifs.rdbuf();
	str = ss.str();

	//process token
	std::string line;
	while(std::getline(ss, line)){
		if(line.compare(0, 7, "uniform") == 0){
			auto begin = line.find_last_of(' ');
			auto end = line.find_last_not_of(';');
			auto name = line.substr(begin+1, end-begin);
			m_nameIdMap[name] = glGetUniformLocation(m_programId, name.c_str());
		}
	}
	return true;
}

GLuint LoadShaders(const std::string& VertexShaderCode, const std::string& FragmentShaderCode){

	// Create the shaders
	GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
	GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

	GLint Result = GL_FALSE;
	int InfoLogLength;

	// Compile Vertex Shader
	char const * VertexSourcePointer = VertexShaderCode.c_str();
	glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
	glCompileShader(VertexShaderID);

	// Check Vertex Shader
	glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
	glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	std::vector<char> VertexShaderErrorMessage(InfoLogLength);
	glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
	fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]);

	// Compile Fragment Shader
	char const * FragmentSourcePointer = FragmentShaderCode.c_str();
	glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
	glCompileShader(FragmentShaderID);

	// Check Fragment Shader
	glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
	glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	std::vector<char> FragmentShaderErrorMessage(InfoLogLength);
	glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
	fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]);

	// Link the program
	fprintf(stdout, "Linking program\n");
	GLuint ProgramID = glCreateProgram();
	glAttachShader(ProgramID, VertexShaderID);
	glAttachShader(ProgramID, FragmentShaderID);
	glLinkProgram(ProgramID);

	// Check the program
	glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
	glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	std::vector<char> ProgramErrorMessage( std::max(InfoLogLength, 1) );
	glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
	fprintf(stdout, "%s\n", &ProgramErrorMessage[0]);

	glDeleteShader(VertexShaderID);
	glDeleteShader(FragmentShaderID);

	return ProgramID;
}

void mgl::Shader::createShader(const std::string& shaderPath)
{
	std::string vertexShaderCode;
	std::string fragmentShaderCode;
	const std::string vertexShaderPath = shaderPath + "_vs.glsl";
	const std::string framentShaderPath = shaderPath + "_fs.glsl";

	processUniformToken(vertexShaderPath, vertexShaderCode);
	processUniformToken(framentShaderPath, fragmentShaderCode);

	m_programId = LoadShaders(vertexShaderCode, fragmentShaderCode);

	for(auto itr : m_nameIdMap){
		m_nameIdMap[itr.first] = glGetUniformLocation(m_programId, itr.first.c_str());
	}

}

void mgl::Shader::releaseShader()
{
	glDeleteProgram(m_programId);
}

void mgl::Shader::use()const
{
	glUseProgram(m_programId);
}

void mgl::Shader::param(const std::string& name, const glm::vec4& value)
{
	glUniform4f(checkName(name), value.x, value.y, value.z, value.w);
}

void mgl::Shader::param(const std::string& name, const glm::vec3& value)
{
	glUniform3f(checkName(name), value.x, value.y, value.z);
}

void mgl::Shader::param(const std::string& name, const glm::mat4& value)
{
	glUniformMatrix4fv(checkName(name), 1, GL_FALSE, &value[0][0]);
}

void mgl::Shader::param(const std::string& name, float value)
{
	glUniform1f(checkName(name), value);
}

void mgl::Shader::param(const std::string& name, int value)
{
	glUniform1i(checkName(name), value);
}

void mgl::Shader::getUniformLocation(const std::string& name)
{
	m_nameIdMap[name] = glGetUniformLocation(m_programId, name.c_str());
}

int mgl::Shader::checkName(const std::string& name) const
{
	const auto itr = m_nameIdMap.find(name);
	if(itr == m_nameIdMap.end()){
		std::cout << "Invalid name:" << name << std::endl;
		return GL_INVALID_VALUE;
	}else{
		return itr->second;
	}
	
}
